## Copyright 2002-2004 Andrew Loewenstern, All Rights Reserved
## Copyright 2007 Roee Shlomo, All Rights Reserved
# see LICENSE.txt for license information

from types import InstanceType
from khash import intify
from const import NULL_ID
from errors import KrpcProtocolError
from BitTornado.clock import clock

DEBUG = False

class Node(object):
    """encapsulate contact info"""
    __slots__ = ('factory', 'fails', 'lastSeen', 'id', 'host', 'port', 'num', '_senderDict')
    def __init__(self, factory):
        self.factory = factory
        self.fails = 0
        self.lastSeen = 0
        self.id = NULL_ID
        self.host = ''
        self.port = ''
    
    def init(self, id, host, port):
        self.id = id
        self.num = intify(id)
        self.host = host
        self.port = port
        self._senderDict = {'id': self.id, 'port' : self.port, 'host' : self.host}
        return self
    
    def initWithDict(self, dict):
        self._senderDict = dict
        self.id = dict['id']
        self.num = intify(self.id)
        self.port = dict['port']
        self.host = dict['host']
        return self
    
    def updateLastSeen(self):
        self.lastSeen = clock()
        self.fails = 0
    
    def msgFailed(self):
        self.fails += 1
        return self.fails
    
    def senderDict(self):
        return self._senderDict
    
    def __repr__(self):
        return repr((self.id, self.host, self.port))
    
    def __lt__(self, a):
        if type(a) == InstanceType:
            a = a.num
        return self.num < a
    def __le__(self, a):
        if type(a) == InstanceType:
            a = a.num
        return self.num <= a
    def __gt__(self, a):
        if type(a) == InstanceType:
            a = a.num
        return self.num > a
    def __ge__(self, a):
        if type(a) == InstanceType:
            a = a.num
        return self.num >= a
    def __eq__(self, a):
        if type(a) == InstanceType:
            a = a.num
        return self.num == a
    def __ne__(self, a):
        if type(a) == InstanceType:
            a = a.num
        return self.num != a
    
class KNode(Node):
    __slots__ = ('factory', 'fails', 'lastSeen', 'id', 'host', 'port', 'num', '_senderDict')
    def checkSender(self, dict):
        try:
            senderid = dict['rsp']['id']
        except KeyError:
            raise KrpcProtocolError("No peer id in response.")
        else:
            if self.id != NULL_ID and senderid != self.id:
                if DEBUG:
                    print "Got response from different node than expected."
                self.factory.table.invalidateNode(self)
        return dict

    def errBack(self, err):
        if DEBUG:
            print "error:", err
        if type(err) is tuple:
            err = err[0]
        return err
        
    def ping(self, id):
        df = self.factory.krpc.sendRequest('ping', {"id":id}, self.host, self.port)
        df.addErrback(self.errBack)
        df.addCallback(self.checkSender)
        return df
    
    def findNode(self, target, id):
        df = self.factory.krpc.sendRequest('find_node', {"target" : target, "id": id}, self.host, self.port)
        df.addErrback(self.errBack)
        df.addCallback(self.checkSender)
        return df
    
    def announcePeer(self, key, value, id):
        token = self.factory.tokensHandler.getToken(id)
        if not token:
            raise KrpcProtocolError("No token for this node.")
        df = self.factory.krpc.sendRequest('announce_peer',
                                           {"info_hash" : key, "port": value, "id": id, "token": token},
                                           self.host, self.port)
        df.addErrback(self.errBack)
        df.addCallback(self.checkSender)
        return df
    
    def getPeers(self, key, id):
        df = self.factory.krpc.sendRequest('get_peers', {"info_hash" : key, "id": id}, self.host, self.port)
        df.addErrback(self.errBack)
        df.addCallback(self.storeToken)
        return df

    def storeToken(self, dict):
        dict = self.checkSender(dict)
        if dict['rsp'].has_key('token'):
            self.factory.tokensHandler.gotToken(dict['rsp']['id'], dict['rsp']['token'])    
        else:
            raise KrpcProtocolError("No token in response.")
        return dict
